package voip;

import java.io.*;
import java.net.*;
import java.util.Scanner;
import voip.SIPProperty;
import voip.SDPparser;
import media.RTPtransmitter;

// SIP Header, SDP Header
public class Session{
	private final int BUF_SIZE = 2048;
	private final int SIP_PORT = 5060;
	
	public InetAddress destAddr;
	public int portNum;

    private int port_num;
    
    private DatagramSocket sip_socket;
    
    public String receiveMsg;
    public DatagramPacket receivePacket;
    public DatagramPacket sendPacket;
    byte data[];
    
    // SIPProperty class
    SIPProperty sipProperty;
    
    // SDPparser class
    SDPparser sdpParser;	
    
    public Session() 
    {
      	port_num = 0;
    	receiveMsg = "";    
    	data = new byte[BUF_SIZE];
		receivePacket = new DatagramPacket(data, data.length );
		sendPacket = new DatagramPacket(data, data.length);
		sdpParser = new SDPparser();
		sipProperty = new SIPProperty();

    }
    
    public Session(String filename, String sip_uri, String http_bind_address)
    {
      	port_num = 0;
    	receiveMsg = "";    
    	data = new byte[BUF_SIZE];
		receivePacket = new DatagramPacket(data, data.length );
		sendPacket = new DatagramPacket(data, data.length);
		sdpParser = new SDPparser();
		

		sipProperty = new SIPProperty(filename, sip_uri, http_bind_address);		
		sipProperty.run(filename);
    }
    
    public void run()
    {
    	System.out.println("SIP/SDP Session Start!");
    	//sipProperty.printDefaultProperty();
    	startUDPSocket();
    	waitForSIPPacket();    	
    }
    /*==========================================================
     * Create a UDP Socket
     *==========================================================*/
    public void startUDPSocket()
    {
    	try
		{
    		// If there is a Command Line Input, then
    		boolean commandInput = sipProperty.getCommandLineStatus();
    		String stringPort = sipProperty.getDefaultConfigValue("sip_port");
    		
    		System.out.println("commandInput: " + commandInput);
    		System.out.println("stringPort: " + stringPort);
    		
    		if(commandInput == true)
    		{
    			try 
    			{
    				
    				port_num = Integer.parseInt(stringPort);
    			
    				System.out.println("port_num:" + port_num);
    			
    				sip_socket = new DatagramSocket(port_num);
    			}
    			catch(NumberFormatException nfe)
    			{
    				System.err.println(nfe);
    				System.exit(1);
    			}
    		}
    		else
    		{
    			sip_socket = new DatagramSocket(SIP_PORT);
    		}
		}
		catch(SocketException socketException)
		{
			socketException.printStackTrace();
			System.exit(1);
		}
    }
    /*==========================================================
     * Receive UDP Packets from SIP clients
     *==========================================================*/    
    public void waitForSIPPacket()
    {
		System.out.println("Waiting for Packets");
		
		while(true)
		{
			try
			{

				sip_socket.receive(receivePacket);
				
				// Old version
				//destAddr = receivePacket.getAddress();
				//portNum = receivePacket.getPort();
				InetAddress client_address = receivePacket.getAddress();
				int client_port = receivePacket.getPort();
				
				System.out.println("[Client]: " + client_address + "[Port]: " + client_port);
				
				/*============================================ 
				 * Create a SenderThread and run a SenderThread
				 *============================================*/
				SenderThread sender = new SenderThread(client_address, 
													client_port, 
													receivePacket);
				sender.start();

			}
			catch(IOException ioException)
			{
				sip_socket.close();
				ioException.printStackTrace();
			}
			finally
			{
				//sip_socket.close();
			}
		}    	
    }
    
}

/*=====================================================================
 * Sender Thread
 * - Send datagram packets to clients
 *=====================================================================*/
class SenderThread extends Thread
{
	private InetAddress client_addr;
	private DatagramSocket client_socket;
	private int port;

	private DatagramPacket datagramRcvPacket;
	private String recvString;
	private InetAddress hostAddr = null;


	private final String CRLF = "\r\n"; 

	// SIP Header Fields
	private String stringFrom;
    private String stringTo;
    private String stringFromTag;
    private String stringBranch;
    private String stringCallSeq;
    private String stringCallID;
   
    private String caller;

	// SDP
    SDPparser sdpParser;	// SDPparser class
    private int sdpLength;
    
    // SIPProperty
    static SIPProperty sipProperty;
    
	// RTP Protocol
    RTPtransmitter rtpTransmitter; //RTPtransmitter
    private String stringRTPport;

	public SenderThread(InetAddress address, int port, DatagramPacket rcvPacket) throws SocketException
	{
		this.client_addr = address;
		this.port = port;
		caller = client_addr.getHostAddress();
		this.client_socket = new DatagramSocket();
		this.client_socket.connect(client_addr, port);
		
		SIPProperty.validUserFlag = false;
			
		this.datagramRcvPacket = rcvPacket;
		sdpParser = new SDPparser();
		//sipProperty = new SIPProperty();
		
		sdpLength = 0;
		// DatagramPacket => String conversion
		recvString = new String(datagramRcvPacket.getData(), 
								0, 
								datagramRcvPacket.getLength());
	}
	
	public void halt()
	{
	}
	
	public InetAddress getHostAddress(){

		try {
			hostAddr = InetAddress.getLocalHost();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return hostAddr;
	}
	public DatagramSocket getSocket()
	{
		return this.client_socket;
	}

	/*=====================================================================
	 * Read SIP Header Information from the received Packet
	 *=====================================================================*/
    public void getSIPHeaderInfo(String msg)
    {
    	String stringLine = "";
    	int start_index, end_index;
    	Scanner scanner = new Scanner(msg);
		
    	while(scanner.hasNext())
    	{
    		stringLine = scanner.nextLine();
    		
    		if(stringLine.startsWith("Via: "))
    		{
    			start_index = stringLine.indexOf("branch=");
    			stringBranch = stringLine.substring(start_index+7);
    			System.out.println("Via: " + stringBranch);
    		}
    		else if(stringLine.startsWith("CSeq: "))
    		{
    			start_index = stringLine.indexOf(' ');
    			stringCallSeq = stringLine.substring(start_index+1);
    			System.out.println("CSeq: " + stringCallSeq);
    		}
    		else if(stringLine.startsWith("Call-ID: "))
    		{
    			start_index = stringLine.indexOf(' ');
    			stringCallID = stringLine.substring(start_index+1);
    			System.out.println("Call-ID: " + stringCallID);
    		}
    		else if(stringLine.startsWith("From: ") == true)
    		{
    			start_index = stringLine.indexOf('"');
    			end_index = stringLine.indexOf(';');
    			
    			stringFrom = stringLine.substring(start_index, end_index);
    			
    			start_index = stringLine.indexOf("tag=");
    			
    			stringFromTag = stringLine.substring(start_index);
    			System.out.println("From: " + stringFrom +" tag: " + stringFromTag);
    		}
    		else if(stringLine.startsWith("To: "))
    		{
    			start_index = stringLine.indexOf('<');
    			end_index = stringLine.indexOf(';');
    			
    			if(end_index == -1)
    				stringTo = stringLine.substring(start_index);
    			else
        			stringTo = stringLine.substring(start_index, end_index);
    			
    			start_index = stringTo.indexOf("sip:");
    			end_index = stringTo.indexOf("@");
    			String user = stringTo.substring(start_index+4, end_index);
    			
    			if(user.compareTo(SIPProperty.default_sip_user) == 0) {
    				SIPProperty.validUserFlag = true;
    			}
    			System.out.println("To: " + stringTo);
    		}
    		else if(stringLine.startsWith("v=")){
    			String sdpBuff = "";
	    		sdpBuff = sdpBuff.concat(stringLine + CRLF);
    	    	while(scanner.hasNext())
    	    	{
    	    		stringLine = scanner.nextLine();
    	    		sdpBuff = sdpBuff.concat(stringLine + CRLF);
    	    	}
	    		sdpParser.parseSDP(sdpBuff);
    		}
    		
    	}
    	scanner.close();
    	
    }

	/*=====================================================================
	 * Analyze Method of SIP Header
	 *=====================================================================*/	
	public void parseMethod(String message)
    {
    	String replyPacket = "";
    	
    	if(message.substring(0,6).contains("INVITE"))
       	{      	  		
    		if(SIPProperty.validUserFlag) {
    			
        		//Get the RTP port from SDP and set up the RTP transmitter
        		stringRTPport = sdpParser.getRTPport();
        		System.out.println("Caller: " + caller + "RTP Port: " + stringRTPport);
        		System.out.println("Message flag: " + SIPProperty.message_flag);
        		
    			// Send "200 OK" message
               	System.out.println("Send 200 OK");
               	replyPacket = makeResponseMsg("200OK", caller);
               	sendPacketToClient(replyPacket);
               	
        		if(SIPProperty.message_flag == 0){
        			rtpTransmitter = new RTPtransmitter(caller, stringRTPport, SIPProperty.default_message);
        		}
        		else {
        			rtpTransmitter = new RTPtransmitter(caller, stringRTPport, SIPProperty.default_message_wav);
        		}
               	
        		//Play the audio file
               	rtpTransmitter.transmit();
               	
       			sendByeMessage();
    		}
    		else {
    			//Send 404 Not Found
               	replyPacket = makeResponseMsg("404NOTFOUND", caller);
               	sendPacketToClient(replyPacket);
    		}

       	}
       	else if(message.substring(0,18).contains("SIP/2.0 100 Trying"))
       	{
       		// Send 180 Ringing message again. 
       		System.out.println("[Received Trying]");
       		replyPacket = makeResponseMsg("INVITE", caller);
           	sendPacketToClient(replyPacket);	// Send Ringing message
           	
           	// Send "200 OK" message
           	System.out.println("Send 200 OK");
           	replyPacket = makeResponseMsg("200OK", caller);
           	sendPacketToClient(replyPacket);
       	}
       	else if(message.substring(0,3).contains("ACK"))
       	{
       		System.out.println("[Received ACK]");
       	}
       	else if(message.substring(0,3).contains("BYE"))
       	{
       		System.out.println("[Received BYE]");
       		System.out.println("Sending 200OKNOSDP");
       		replyPacket = makeResponseMsg("200OKNOSDP", caller);
       		sendPacketToClient(replyPacket);
       	}
       	else
       	{
       		System.out.println("[Other messages]");
       		replyPacket = makeResponseMsg("ERROR", caller);
       	}
    }
	/*=====================================================================
	 * Send BYE message after RTP session
	 *=====================================================================*/
	public void sendByeMessage()
	{
		String stringBye = "";
   		System.out.println("[Send BYE Message]");
   		stringBye = makeResponseMsg("BYE", caller);
       	sendPacketToClient(stringBye);	// Send Ringing message
	}
	
    public String makeResponseMsg(String method, String src_addr)
    {
    	String sipStartLine = "";
    	String sipHeader = "";
    	String sipData = "";
    	String sdpBody = "";
    	// Parsing From, To, 
    	sipStartLine = setStartLine(method);
		
    	if(method == "INVITE")
    	{
        	sdpLength = 0;
    		// In 180 Ringing message, there is no need SDP Body
        	sipHeader = setSipHeader(method, src_addr);
    		sipData = sipStartLine + sipHeader;    		
    	}
    	else if(method == "200OK")
    	{
    		sdpBody = sdpParser.createSDPreply();
    		sdpLength = sdpBody.length();
        	sipHeader = setSipHeader(method, src_addr);
    		sipData = sipStartLine + sipHeader + sdpBody;
    	}
    	else if(method == "200OKNOSDP")
    	{
        	sdpLength = 0;
        	sipHeader = setSipHeader("200OK", src_addr);
    		sipData = sipStartLine + sipHeader; 
    	}
    	else if(method == "BYE")
    	{
    		sipHeader = setServerByeSipHeader("BYE", src_addr);
    		sipData = sipStartLine + sipHeader;
    	}
    	else if(method == "404NOTFOUND"){
    		sipHeader = setSipHeader("404NOTFOUND", src_addr);
    		sipData = sipStartLine + sipHeader;
    	}
    	return sipData;
    }
  
    public String setStartLine(String method)
    {
    	String reply = "";
    	int sidx, eidx;
    	   	
    	if(method == "INVITE")
    		reply = "SIP/2.0 180 Ringing" + CRLF;
    	else if(method == "ACK")
    		reply = "SIP/2.0 200 OK" + CRLF;
    	else if(method == "200OK")
    		reply = "SIP/2.0 200 OK" + CRLF;
    	else if(method == "BYE") {
    		sidx = stringFrom.indexOf("<");
    		eidx = stringFrom.indexOf(">");
    		
    		if((sidx != -1) && (eidx != -1))
    			stringFrom = stringFrom.substring(sidx+1, eidx);
    		reply = "BYE " + stringFrom + " SIP/2.0" + CRLF;
    	}
    	else if(method == "404NOTFOUND")
    		reply = "SIP/2.0 404 Not Found" + CRLF;
    	else
    		reply = "SIP/2.0 200 OK" + CRLF;
    	    	
    	return reply;    	
    }
    
    public String setSipHeader(String method, String src_addr) {   	
        String header = "Via: SIP/2.0/UDP " + 	hostAddr + ":5060;" + "branch=" + stringBranch + CRLF
                + "From: " + stringFrom + ";" + stringFromTag + CRLF
                + "To: " + stringTo + ";tag=123456789" + CRLF
                + "Call-ID: " + stringCallID + CRLF
                + "CSeq: " + stringCallSeq + CRLF
                + "Allow: INVITE, ACK, BYE" + CRLF
                + "Supported: replaces, timer" + CRLF
                + "Max-Forwards: 70" + CRLF
                + "Accept: application/sdp" + CRLF
                + "Contact: " + stringTo + ":5060" + CRLF
                + "Content-Type: application/sdp" + CRLF
                + "Content-Length: " + sdpLength + CRLF + CRLF;
        return header;
    }	
	
    public String setServerByeSipHeader(String method, String src_addr) {
    	int Cseq = 0;
		int index = stringCallSeq.indexOf(" ");
		InetAddress hostAddr = getHostAddress();
		
		stringCallSeq = stringCallSeq.substring(0, index);
		
    	Cseq = Integer.parseInt(stringCallSeq) + 1;
        String header = "Via: SIP/2.0/UDP " + hostAddr.getHostAddress()+ ";rport;" + "branch=" + stringBranch + "by353rv3r" + CRLF
                + "To: " + stringFrom + ";" + stringFromTag + CRLF
                + "From: " + stringTo + ";tag=123456789" + CRLF
                + "Call-ID: " + stringCallID + CRLF
                + "CSeq: " + Cseq + " BYE" + CRLF
                + "Allow: INVITE, ACK, BYE" + CRLF
                + "Max-Forwards: 70" + CRLF
                + "Content-Length: 0" + CRLF + CRLF;
        return header;
    }
    
	/*=====================================================================
	 * Send Datagram Packet to a client
	 *=====================================================================*/
	void sendPacketToClient(String stringMessage)
	{
		try 
		{
			byte[] sendByte = stringMessage.getBytes();			
			DatagramPacket sendPacket = new DatagramPacket(sendByte, 
														sendByte.length, 
														client_addr, 
														port);
		
			client_socket.send(sendPacket);
		
			/* Debugging */
			System.out.println(sendPacket);
		}
		catch(IOException e)
		{
			System.out.println(e);
		}
	}
	
	public void printSIPProperty()
	{
		System.out.println("[ Sender Thread Default SIP Property ]");
				
		System.out.println("default_message: " + sipProperty.default_message);
		System.out.println("default_message: " + sipProperty.default_message_wav);
		System.out.println("default_message: " + sipProperty.default_message_text);
		System.out.println("default_message: " + sipProperty.default_sip_interface);
		System.out.println("default_message: " + sipProperty.default_sip_user);
		System.out.println("default_message: " + sipProperty.default_sip_port);
		System.out.println("default_message: " + sipProperty.default_sip_uri);
		System.out.println("default_message: " + sipProperty.default_http_interface);
		System.out.println("default_message: " + sipProperty.default_http_port);
		System.out.println("default_message: " + sipProperty.default_http_bind_address);
		System.out.println("=================================================");

	}
	public void run()
	{
		System.out.println("Sender Thread starts...");			
		
		//printSIPProperty();
		// Get From, To, Call-ID string from received string
   		getSIPHeaderInfo(recvString); 
   		
   		boolean commandInput = sipProperty.boolCommandInput;
   		
   		if(commandInput == true)
   		{
   			String sip_user = sipProperty.getDefaultConfigValue("default_sip_uri");
   			
   			if(sip_user == stringTo)
   				parseMethod(recvString);
   			else
   			{
   				System.out.println("sip_user: " + sip_user);
   				System.out.println("stringTo: " + stringTo);
   				
   				System.out.println("[Error]: SIP User is different. Now terminate.");
   				System.exit(0);
   			}
   		}
   		else
   		{
	   		   		
   			parseMethod(recvString);
   		}
       	
		Thread.yield();
	}
	
}
